﻿// -----------------------------------------------------------------------
// <copyright file="BaseForm.cs" company="Abyssalsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Gui.BaseForm
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Xml;
    using Gui.Presentation;
    using Gui.ThemeEngine;
    using Metal.Config;

    /// <summary>
    /// The base form that contains common methods for all the forms. It also read the theme xml configuration and load objects into form
    /// </summary>
    public partial class BaseForm : Form
    {
        #region Members

        /// <summary>Used for moving forms</summary>
        private const int WMNCHITTEST = 0x84;

        /// <summary>Used for moving forms</summary>
        private const int HTCLIENT = 0x1;

        /// <summary>Used for moving forms</summary>
        private const int HTCAPTION = 0x2;

        /// <summary>Used for moving forms</summary>
        private const int WMMOVE = 0x3;

        /// <summary>Used for resizing forms</summary>
        //private const int HTTOP = 12;

        /// <summary>Used for resizing forms</summary>
        private const int HTLEFT = 10;

        /// <summary>Used for resizing forms</summary>
        private const int HTRIGHT = 11;

        /// <summary>Used for resizing forms</summary>
        private const int HTBOTTOM = 15;

        /// <summary>Used for resizing forms</summary>
        private const int HTBOTTOMLEFT = 16;

        /// <summary>Used for moving forms</summary>
        private const int HTBOTTOMRIGHT = 17;

        /// <summary>Right click on taskbar</summary>
        //private const int WMTaskbarRClick = 0x0313;

        private const int WS_MINIMIZEBOX = 0x20000;
        private const int CS_DBLCLKS = 0x8;

        /// <summary>Mouse down</summary>
        private const int WMMOUSEDOWN = 0xa1;

        /// <summary>Mouse up</summary>
        private const int WMMOUSEUP = 0xa2;

        /// <summary>Window command</summary>
        private const int WM_SYSCOMMAND = 0x0112;

        /// <summary>The window is restored</summary>
        private const int SC_RESTORE = 0xF120;

        /// <summary>Stores the path to our configuration xml file</summary>
        private string strConfigFile;

        /// <summary>Stores the theme name</summary>
        private string strThemeName;

        /// <summary>Stores the name of the form</summary>
        private string strHostName;

        /// <summary>Rounded corners</summary>
        private int intSmoothCorners;

        /// <summary>Object to load our xml configuration file</summary>
        private XmlDocument xmlConfig = new XmlDocument();

        /// <summary>Form snap class</summary>
        private StickyWindow stickyWindow;

        private OnCloseEnum onClose;

        private ThemeEngine.ControlBuilder builder;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the BaseForm class.
        /// </summary>
        public BaseForm()
        {
            this.InitializeComponent();

            // Fixing if the user changes DPI size:
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
        }

        #endregion

        #region Enums

        public enum OnCloseEnum
        {
            CloseApp,
            CloseForm,
            Hide
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Load form controls and apply theme
        /// </summary>
        public void ApplyTheme()
        {
            try
            {
                this.LoadThemeConfig();

                this.LoadFormProperties();

                builder = new ThemeEngine.ControlBuilder();

                // Create panels and tab controls (the main containers)
                XmlNode panels = this.xmlConfig.SelectSingleNode("Form/Panels");
                if (panels != null)
                {
                    foreach (XmlNode panel in panels.ChildNodes)
                    {
                        string controlType = PropertyProvider.GetValue(panel, "type", string.Empty);

                        if (controlType == "Panel" || controlType == "GroupBox")
                        {
                            this.BuildPanel(panel);
                        }
                        else if (controlType == "TabControl")
                        {
                            this.BuildTabControl(panel);
                        }
                        else if (controlType == "Spliter")
                        {
                            this.BuildSpliter(panel);
                        }
                    }
                }

                // Create minimize / close buttons
                XmlNode minimizeButton = this.xmlConfig.SelectSingleNode("Form/MinimizeButton");
                if (minimizeButton != null)
                {
                    this.Controls.Add(builder.CreateControl(minimizeButton));
                    this.Controls["Minimize"].BringToFront();
                }

                XmlNode closeButton = this.xmlConfig.SelectSingleNode("Form/CloseButton");
                if (closeButton != null)
                {
                    this.Controls.Add(builder.CreateControl(closeButton));
                    this.Controls["Close"].BringToFront();
                }

                // Load settings
                this.LoadFormSettings();

                // Now that we have all the controls builded, subscribe to controls events
                this.SubscribeToEvents();
            }
            catch (Exception ex)
            {
                Metal.User.UserMessage.ShowError(ex);
            }
        }

        private void BuildPanel(XmlNode panel)
        {
            string controlType;
            this.Controls.Add(builder.CreateControl(panel));

            // If is a panel, it may be possible to have childrens.
            foreach (XmlNode childrenControl in panel.ChildNodes)
            {
                controlType = PropertyProvider.GetValue(childrenControl, "type", string.Empty);

                // Ignore ContextMenu because is not a Top control, it will not be created here
                if (controlType != "ContextMenu")
                {
                    // Create controls in this panel
                    this.Controls.Add(builder.CreateControl(childrenControl));

                    // Set this Panel as parent (only if the panel is enabled)
                    if (this.Controls[panel.Name].Enabled == true)
                    {
                        this.Controls[PropertyProvider.GetValue(childrenControl, "command", childrenControl.Name)].Parent = this.Controls[panel.Name];
                    }
                    else
                    {
                        this.Controls[childrenControl.Name].BringToFront();
                    }
                }
            }

            // The main panel
            string includeMask = PropertyProvider.GetValue(panel, "includeMask", "False");
            if (includeMask != "False")
            {
                // Get mask
                System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(PropertyProvider.GetValue(panel, "maskNamespace", "Control not found!"));
                Type t = assembly.GetType(PropertyProvider.GetValue(panel, "className", "Control not found!"));
                UserControl skinableMask = (UserControl)Activator.CreateInstance(t);
                skinableMask.Dock = DockStyle.Fill;

                this.Controls[panel.Name].Controls.Add(skinableMask);
                this.Controls[panel.Name].BringToFront();
            }
        }

        private void BuildTabControl(XmlNode panel)
        {
            Gui.Controls.TabControl.MetalTabControl tabControl = (Gui.Controls.TabControl.MetalTabControl)builder.CreateControl(panel);

            this.Controls.Add(tabControl);
            tabControl.BringToFront();

            string includeMask = PropertyProvider.GetValue(panel, "includeMask", "False");
            if (includeMask != "False")
            {
                // Get the mask that we must display on the first tab. The others masks will be loaded on tab click event.
                System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(PropertyProvider.GetValue(panel.FirstChild, "maskNamespace", "Control not found!"));
                Type t = assembly.GetType(PropertyProvider.GetValue(panel.FirstChild, "className", "Control not found!"));
                UserControl skinableMask = (UserControl)Activator.CreateInstance(t);
                skinableMask.Dock = DockStyle.Fill;

                tabControl.TabPages[0].Controls.Add(skinableMask);
            }
        }

        private void BuildSpliter(XmlNode panel)
        {
            string controlType;
            Gui.Controls.MetalSplitContainer splitControl = (Gui.Controls.MetalSplitContainer)builder.CreateControl(panel);

            this.Controls.Add(splitControl);
            splitControl.BringToFront();

            splitControl.SplitterDistance = PropertyProvider.GetValue(panel, "splitterDistance", 200);
            string fixedPanel = PropertyProvider.GetValue(panel, "splitterDistance", "Panel1");
            if (fixedPanel == "Panel1")
            {
                splitControl.FixedPanel = FixedPanel.Panel1;
            }
            else
            {
                splitControl.FixedPanel = FixedPanel.Panel2;
            }

            // Panel1
            foreach (XmlNode childrenControl in panel.ChildNodes[0])
            {
                controlType = PropertyProvider.GetValue(childrenControl, "type", string.Empty);

                // Ignore ContextMenu because is not a Top control, it will not be created here
                if (controlType != "ContextMenu")
                {
                    splitControl.Panel1.Controls.Add(builder.CreateControl(childrenControl));
                }

                string includeMask = PropertyProvider.GetValue(childrenControl, "includeMask", "False");
                if (includeMask != "False")
                {
                    // Get the mask that we must display on the first tab. The others masks will be loaded on tab click event.
                    System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(PropertyProvider.GetValue(childrenControl, "maskNamespace", "Control not found!"));
                    Type t = assembly.GetType(PropertyProvider.GetValue(childrenControl, "className", "Control not found!"));
                    UserControl skinableMask = (UserControl)Activator.CreateInstance(t);
                    skinableMask.Dock = DockStyle.Fill;

                    splitControl.Panel1.Controls.Add(skinableMask);
                    skinableMask.BringToFront();
                }
            }

            // Panel2
            foreach (XmlNode childrenControl in panel.ChildNodes[1])
            {
                controlType = PropertyProvider.GetValue(childrenControl, "type", string.Empty);

                // Ignore ContextMenu because is not a Top control, it will not be created here
                if (controlType != "ContextMenu")
                {
                    splitControl.Panel2.Controls.Add(builder.CreateControl(childrenControl));
                }

                string includeMask = PropertyProvider.GetValue(panel, "includeMask", "False");
                if (includeMask != "False")
                {
                    // Get the mask that we must display on the first tab. The others masks will be loaded on tab click event.
                    System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(PropertyProvider.GetValue(childrenControl, "maskNamespace", "Control not found!"));
                    Type t = assembly.GetType(PropertyProvider.GetValue(childrenControl, "className", "Control not found!"));
                    UserControl skinableMask = (UserControl)Activator.CreateInstance(t);
                    skinableMask.Dock = DockStyle.Fill;

                    splitControl.Panel2.Controls.Add(skinableMask);
                    skinableMask.BringToFront();
                }
            }
        }

        private void LoadThemeConfig()
        {
            // The mask name is something like: PlaylistForm, we remove "Form".
            this.strHostName = this.Name.Replace("Form", string.Empty);

            // Get theme name (Default: BlackMetal)
            this.strThemeName = Settings.Instance.GetValue("LastSettings.Theme", "BlackMetal");

            // Load theme configuration from xml
            try
            {
                this.strConfigFile = Paths.ThemesFolder + this.strThemeName + "\\" + this.strHostName + ".xml";
                this.xmlConfig.Load(this.strConfigFile);
            }
            catch
            {
                this.xmlConfig.LoadXml("<Form></Form>");
            }
        }

        private void LoadFormProperties()
        {
            // Get form properties
            XmlNode form = this.xmlConfig.SelectSingleNode("Form");
            if (form != null)
            {
                string onCloseBehaviour = PropertyProvider.GetValue(form, "onClose", "CloseForm");

                if (onCloseBehaviour == "CloseApp")
                {
                    this.onClose = OnCloseEnum.CloseApp;
                }
                else if (onCloseBehaviour == "CloseForm")
                {
                    this.onClose = OnCloseEnum.CloseForm;
                }
                else if (onCloseBehaviour == "Hide")
                {
                    this.onClose = OnCloseEnum.Hide;
                }

                this.BackColor = PropertyProvider.GetValue(form, "backColor");

                this.MinimumSize = new Size(PropertyProvider.GetValue(form, "minWidth", 100), PropertyProvider.GetValue(form, "minHeight", 50));
                if (PropertyProvider.GetValue(form, "maxWidth", -1) != -1)
                {
                    this.MaximumSize = new Size(PropertyProvider.GetValue(form, "maxWidth", 100), PropertyProvider.GetValue(form, "maxHeight", 50));
                }

                this.intSmoothCorners = PropertyProvider.GetValue(form, "roundCorners", 9);

                // Apply transparency key
                string strTransparency = PropertyProvider.GetValue(form, "transparencyKey", string.Empty);
                if (strTransparency != string.Empty)
                {
                    this.TransparencyKey = PropertyProvider.GetValue(form, "transparencyKey");
                }

                // Apply transparency
                float transparency = (float)Settings.Instance.GetValue("LastSettings.Transparency", 1);

                if (transparency != 100)
                {
                    this.Opacity = transparency / 100;
                }
            }
        }

        #endregion

        #region Private methods

        /// <summary>Used to move and resize form</summary>
        /// <param name="m">Window message</param>
        //[System.Diagnostics.DebuggerStepThrough()]
        protected override void WndProc(ref Message m)
        {
            try
            {
                base.WndProc(ref m);

                // Move
                if (m.Msg == WMNCHITTEST && m.Result.ToInt32() == HTCLIENT)
                {
                    m.Result = (IntPtr)HTCAPTION;
                }

                // Resize
                if (m.Msg == WMNCHITTEST)
                {
                    int x = (int)(m.LParam.ToInt64() & 0xFFFF);
                    int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
                    Point pt = PointToClient(new Point(x, y));
                    Size clientSize = ClientSize;

                    // Bottom right
                    if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
                    {
                        m.Result = (IntPtr)(IsMirrored ? HTBOTTOMLEFT : HTBOTTOMRIGHT);
                        return;
                    }

                    // Bottom left
                    if (pt.X <= 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= clientSize.Height - 16)
                    {
                        m.Result = (IntPtr)(IsMirrored ? HTBOTTOMRIGHT : HTBOTTOMLEFT);
                        return;
                    }

                    // Bottom
                    if (pt.X > 16 && pt.X <= clientSize.Width - 16 && pt.Y >= clientSize.Height -16)
                    {
                        m.Result = (IntPtr)HTBOTTOM;
                        return;
                    }

                    // Left
                    if (pt.X < 16 && pt.Y >= 16)
                    {
                        m.Result = (IntPtr)HTLEFT;
                        return;
                    }

                    // Right
                    if (pt.X > clientSize.Width - 16 && pt.Y >= 16)
                    {
                        m.Result = (IntPtr)HTRIGHT;
                        return;
                    }

                    //// Top
                    //if (pt.X > 16 && pt.X <= clientSize.Width - 16 && pt.Y <= 16)
                    //{
                    //    m.Result = (IntPtr)HTTOP;
                    //    return;
                    //}
                }

                //if (m.Msg == WM_SYSCOMMAND && (int)m.WParam == SC_RESTORE)
                //{
                //    // When the window is restored (from minimized state), make the forms with rounded corners... again.
                //    foreach (Form form in Application.OpenForms)
                //    {
                //        FormSmooth.SmoothForm(form, 9);
                //    }
                //}
            }
            catch
            {
            }
        }

        ///// <summary>
        ///// This stops the form flicker
        ///// </summary>
        //protected override CreateParams CreateParams
        //{
        //    get
        //    {
        //        CreateParams cp = base.CreateParams;
        //        cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
        //        //cp.Style |= 0xA0000; // Turn on WS_SYSMENU and Minimize (but it fucks the resize)
        //        return cp;
        //    }
        //}

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;

                // This allows user to minimize the player by clicking on the taskbar (normal window behaviour)
                cp.Style |= WS_MINIMIZEBOX;
                cp.ClassStyle |= CS_DBLCLKS;
                return cp;
            }
        }

        /// <summary>
        /// Subscribe to events
        /// </summary>
        private void SubscribeToEvents()
        {
            try
            {
                if (this.Controls["Minimize"] != null)
                {
                    (this.Controls["Minimize"] as UserControl).Click += new EventHandler(this.OnMinimizeClick);
                }

                if (this.Controls["Close"] != null)
                {
                    (this.Controls["Close"] as UserControl).Click += new EventHandler(this.OnCloseClick);
                }
            }
            catch (Exception ex)
            {
                Metal.User.UserMessage.ShowError(ex);
            }
        }

        /// <summary>
        /// Load form last position, size etc
        /// </summary>
        private void LoadFormSettings()
        {
            try
            {
                string playerStyle = "Config." + Settings.Instance.GetValue("LastSettings.MainFormStyle", "Audio");

                this.Top = ThemeSettings.Instance.GetValue(playerStyle + "." + this.Name + ".Top", 0);
                this.Left = ThemeSettings.Instance.GetValue(playerStyle + "." + this.Name + ".Left", 0);
                this.Width = ThemeSettings.Instance.GetValue(playerStyle + "." + this.Name + ".Width", 300);
                this.Height = ThemeSettings.Instance.GetValue(playerStyle + "." + this.Name + ".Height", 100);

                bool visible = ThemeSettings.Instance.GetValue(playerStyle + "." + this.Name + ".Visible", true);
                if (visible == false)
                {
                    this.Show();
                    this.Visible = false;
                }
                else
                {
                    this.Show();
                }
            }
            catch (Exception ex)
            {
                Metal.User.UserMessage.ShowError(ex);
            }
        }

        private void SetRoundedCorners()
        {
            // Refresh rounded corners
            if (this.WindowState == FormWindowState.Normal)
            {
                FormSmooth.SmoothForm(this, this.intSmoothCorners);
            }
            else if (this.WindowState == FormWindowState.Maximized)
            {
                FormSmooth.SmoothForm(this, 0);
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the form is loaded
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event args</param>
        private void BaseForm_Load(object sender, EventArgs e)
        {
            try
            {
                this.FormBorderStyle = FormBorderStyle.None;

                // Refresh rounded corners
                FormSmooth.SmoothForm(this, this.intSmoothCorners);

                // If is in designer the sticky window acts strange and you can move the form :)
                if (this.DesignMode)
                {
                    return;
                }

                // Form Snap
                this.stickyWindow = new StickyWindow(this);
            }
            catch (Exception ex)
            {
                Metal.User.UserMessage.ShowError(ex);
            }
        }

        /// <summary>
        /// Occurs when the form is resized
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event args</param>
        private void BaseForm_Resize(object sender, EventArgs e)
        {
            this.SetRoundedCorners();
        }

        private void BaseForm_Paint(object sender, PaintEventArgs e)
        {
            this.SetRoundedCorners();
        }

        /// <summary>
        /// Occurs when the form is shown
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event args</param>
        private void BaseForm_Shown(object sender, EventArgs e)
        {
            if (this.Modal == true)
            {
                foreach (Form form in Application.OpenForms)
                {
                    // This is modal, so make the other forms transparent
                    if (form.Name != this.Name)
                    {
                        form.Opacity = (float)0.7;
                    }
                }
            }
        }

        /// <summary>
        /// Save form position and size
        /// </summary>
        /// <param name="sender"> Object sender </param>
        /// <param name="e"> Event arguments </param>
        private void BaseForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                string playerStyle = "Config." + Settings.Instance.GetValue("LastSettings.MainFormStyle", "Audio");

                if (this.Top > 0)
                {
                    ThemeSettings.Instance.SetValue(playerStyle + "." + this.Name + ".Top", this.Top);
                }

                if (this.Left > 0)
                {
                    ThemeSettings.Instance.SetValue(playerStyle + "." + this.Name + ".Left", this.Left);
                }

                ThemeSettings.Instance.SetValue(playerStyle + "." + this.Name + ".Width", this.Width);
                ThemeSettings.Instance.SetValue(playerStyle + "." + this.Name + ".Height", this.Height);
                ThemeSettings.Instance.SetValue(playerStyle + "." + this.Name + ".Visible", this.Visible);

                if (this.Modal == true)
                {
                    foreach (Form form in Application.OpenForms)
                    {
                        form.Opacity = 100;
                    }
                }
            }
            catch (Exception ex)
            {
                Metal.User.UserMessage.ShowError(ex);
            }
        }

        /// <summary>
        /// Occurs when the minimize button is clicked
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event args</param>
        private void OnMinimizeClick(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;
        }

        /// <summary>
        /// Occurs when the close button is clicked
        /// </summary>
        /// <param name="sender">Object sender</param>
        /// <param name="e">Event args</param>
        private void OnCloseClick(object sender, EventArgs e)
        {
            if (this.onClose == OnCloseEnum.CloseApp)
            {
                //this.Close();
                //System.Windows.Forms.Application.Exit();
                Metal.Controller.Accesibility.CloseApplication();
            }
            else if (this.onClose == OnCloseEnum.CloseForm)
            {
                this.Close();
                this.Dispose();
            }
            else if (this.onClose == OnCloseEnum.Hide)
            {
                this.Visible = false;
            }
        }

        #endregion
    }
}
